﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Timers;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using FlanProjectData.Managers;
using FlanProjectData.GameObjects;
using FlanProjectData.Sprites;
using FlanProjectData.Helpers;
using FlanProjectData.Characters;

namespace FlanProject.Combats
{
    public class CombatEngine
    {
        #region Starting Positions

        private static readonly Vector2[] PlayerStartingPositions = new Vector2[4]
        {
            new Vector2(100f, 450f),
            new Vector2(140f, 450f),
            new Vector2(180f, 450f),
            new Vector2(220f, 450f)
        };

        private static readonly Vector2[] MonsterStartingPositions = new Vector2[9]
        {
            new Vector2(700f, 450f),
            new Vector2(660f, 450f),
            new Vector2(620f, 450f),
            new Vector2(680f, 450f),
            new Vector2(640f, 450f),
            new Vector2(600f, 450f),
            new Vector2(580f, 450f),
            new Vector2(560f, 450f),
            new Vector2(540f, 450f)
        };

        #endregion

        #region Combat Data

        private static SpriteFont font;
        private static List<CombatEffect> effects = new List<CombatEffect>();
        private static List<CombatEffect> completedEffects = new List<CombatEffect>();

        public static Image Background { get; set; }
        public static List<CombatPlayer> Players { get; set; }
        public static List<CombatMonster> Monsters { get; set; }

        #endregion

        // Initialize the characters and the combo system
        public static void Initialize()
        {
            SequenceManager.Initialize();

            Players = new List<CombatPlayer>();
            Monsters = new List<CombatMonster>();

            Monsters.Add(new CombatMonster(new Monster() { Name = "Wolf" }));
            Monsters.Add(new CombatMonster(new Monster() { Name = "Wolf" }));
            Monsters.Add(new CombatMonster(new Monster() { Name = "Wolf" }));
            Monsters.Add(new CombatMonster(new Monster() { Name = "Wolf" }));
            Monsters.Add(new CombatMonster(new Monster() { Name = "Cactus" }));
            Monsters.Add(new CombatMonster(new Monster() { Name = "Cactus" }));
            Monsters.Add(new CombatMonster(new Monster() { Name = "Thief" }));
            Monsters.Add(new CombatMonster(new Monster() { Name = "Thief" }));
            Monsters.Add(new CombatMonster(new Monster() { Name = "Thief" }));
        }

        // Load the background and the characters
        public static void LoadContent(ContentManager contentManager)
        {
            // Load background
            Background = contentManager.Load<Image>(@"Data\Backgrounds\" + "Desert");
            Background.LoadContent(contentManager);

            // Load font
            CombatEffect.LoadContent(contentManager);
            font = contentManager.Load<SpriteFont>(@"Sprites\Fonts\CombatFont");

            // Load players
            for (var i = 0; i < Players.Count; i++)
            {
                Players[i].LoadContent(contentManager);
                Players[i].Stats.CurrentHealthPoints = Players[i].Stats.MaxHealthPoints;
                Players[i].Stats.CurrentManaPoints = Players[i].Stats.MaxManaPoints;
            }

            // Load monsters
            for (var i = 0; i < Monsters.Count; i++)
            {
                var monster = contentManager.Load<Monster>(@"Data\Characters\Monsters\" + Monsters[i].Name);
                Monsters[i] = new CombatMonster(monster);
                Monsters[i].LoadContent(contentManager);
                Monsters[i].PlayerIndex = i;
            }

            Reset();
        }

        // Update the user input, the characters, the combo system, the collision data and the artificial intelligence
        public static void Update(GameTime gameTime)
        {
            foreach (var player in Players)
            {
                ApplyCollisions(gameTime, player, Monsters);
                player.Update(gameTime);
            }

            foreach (var monster in Monsters)
            {
                ApplyCollisions(gameTime, monster, Players);
                monster.Update(gameTime);
            }

            foreach (var effect in effects)
            {
                effect.Update(gameTime);
            }

            // End combat
            if (AreMonstersDefeated())
            {
                foreach (var player in Players)
                {
                    player.CurrentState = CombatCharacter.States.Victory;
                }
            }
            else if (ArePlayersDefeated())
            {
                Console.WriteLine("Game Over !");
            }
        }

        // Draw the animation used
        public static void Draw(SpriteBatch spriteBatch, GameTime gameTime)
        {
            var width = spriteBatch.GraphicsDevice.Viewport.Width;
            var height = spriteBatch.GraphicsDevice.Viewport.Height;

            Background.Draw(spriteBatch, Vector2.Zero, true);

            foreach (var monster in Monsters)
            {
                monster.Draw(spriteBatch, gameTime);
            }

            foreach (var player in Players)
            {
                spriteBatch.DrawString(font, "HP " + player.Stats.CurrentHealthPoints.ToString(),
                    new Vector2((float)(width * 0.06 + width * 0.2 * player.PlayerIndex), (float)(height * 0.03)), Color.White);

                spriteBatch.DrawString(font, "MP " + player.Stats.CurrentManaPoints.ToString(),
                    new Vector2((float)(width * 0.06 + width * 0.2 * player.PlayerIndex), (float)(height * 0.06)), Color.White);

                player.Draw(spriteBatch, gameTime);

                // Draw spell after spellcasting ends
                if (player.PlayerMove != null && player.IsCastTimeOver)
                {
                    foreach (var monster in Monsters)
                    {
                        if (monster.CurrentState != CombatCharacter.States.Dead)
                        {
                            if (monster.CollisionVector == Vector2.Zero)
                                monster.CollisionVector = monster.Position;

                            player.PlayerMove.Draw(spriteBatch, SpriteEffects.None, monster.CollisionVector, gameTime);
                        }
                    }

                    Console.WriteLine("Current Animation : " + player.PlayerMove.CurrentAnimation.FrameIndex);

                    // Expire spell after use
                    if (player.PlayerMove.CurrentAnimation.IsAnimationComplete)
                    {
                        player.PlayerMove.CurrentAnimation.Reset();
                        player.IsCastTimeOver = false;
                        player.PlayerMove = null;

                        foreach (var monster in Monsters)
                        {
                            monster.CollisionVector = Vector2.Zero;
                        }
                    }
                }
            }

            foreach (var effect in effects)
            {
                effect.Draw(spriteBatch);
            }

            // Remove complete combat effects
            Predicate<CombatEffect> completeEffects = delegate(CombatEffect combatEffect)
            {
                return combatEffect.IsRiseComplete;
            };

            effects.RemoveAll(completeEffects);
        }

        // Reset combat
        public static void Reset()
        {
            for (int i = 0; i < Players.Count; i++)
            {
                if (Players[i].CurrentState != CombatCharacter.States.Dead)
                {
                    Players[i].CurrentState = CombatCharacter.States.Idle;
                    Players[i].Position = new Vector2(PlayerStartingPositions[i].X, PlayerStartingPositions[i].Y - Players[i].HeightOffset);

                    if (Players[i].PlayerIndex != 0)
                    {
                        foreach (var monster in Monsters)
                        {
                            Players[i].ArtificialIntelligence.Targets.Add(monster);
                        }
                    }
                }
            }

            for (int i = 0; i < Monsters.Count; i++)
            {
                Monsters[i].Action = null;
                Monsters[i].CurrentState = CombatCharacter.States.Idle;
                Monsters[i].Position = new Vector2(MonsterStartingPositions[i].X, MonsterStartingPositions[i].Y - Monsters[i].HeightOffset);
                Monsters[i].Stats.CurrentHealthPoints = Monsters[i].Stats.MaxHealthPoints;

                foreach (var player in Players)
                {
                    Monsters[i].ArtificialIntelligence.Targets.Add(player);
                }
            }

            effects.Clear();
        }

        // Combat is lost if all the players are dead
        public static bool ArePlayersDefeated()
        {
            var isKnockedOut = true;

            foreach (var character in Players)
            {
                if (character.CurrentState != CombatCharacter.States.Dead)
                {
                    isKnockedOut = false;
                }
            }

            return isKnockedOut;
        }

        // Combat is won if all the monsters are dead
        public static bool AreMonstersDefeated()
        {
            var isKnockedOut = true;

            foreach (var monster in Monsters)
            {
                if (monster.CurrentState != CombatCharacter.States.Dead)
                {
                    isKnockedOut = false;
                }
            }

            return isKnockedOut;
        }

        // Add members to party
        public static void LoadPartyMembers(Party party)
        {
            Players = new List<CombatPlayer>();

            var index = 0;

            foreach (var player in party.PlayerList)
            {
                if (Players.Count <= PlayerStartingPositions.Length)
                {
                    var newPlayer = new CombatPlayer(player);
                    newPlayer.PlayerIndex = index;
                    Players.Add(newPlayer);

                    index++;
                }
            }
        }

        // Add effects
        public static void ApplyCollisionEffects(CombatCharacter character, CombatCharacter enemy)
        {
            if (character.CurrentState == CombatCharacter.States.Attacking && enemy.CurrentState != CombatCharacter.States.Hit
                            && enemy.CurrentState != CombatCharacter.States.Dead)
                effects.Add(new CombatEffect(enemy.Position, enemy.ReceiveDamage(character)));
        }

        // Restrict the characters' movements
        public static void ApplyCollisions<T>(GameTime gameTime, CombatCharacter character, List<T> enemies) where T : CombatCharacter
        {
            var currentAnimation = character.CurrentAnimation;

            // Restrict the movements to the background
            character.Position = new Vector2(MathHelper.Clamp(character.Position.X,
                FlanProject.Instance.GraphicsDevice.Viewport.Bounds.Left, FlanProject.Instance.GraphicsDevice.Viewport.Bounds.Right
                    - currentAnimation.Animation.FrameList[currentAnimation.FrameIndex].Width), character.Position.Y);

            var characterRectangle = GetCollisionData(gameTime, character);
            var collisionRectangle = characterRectangle;
            collisionRectangle.Inflate(-10, 0);

            foreach (var enemy in enemies)
            {
                var enemyRectangle = GetCollisionData(gameTime, enemy);
                var enemyCollisionRectangle = enemyRectangle;
                enemyCollisionRectangle.Inflate(-15, 0);

                // Cannot move through enemies
                if (collisionRectangle.Intersects(enemyCollisionRectangle))
                {
                    /* if (enemy.CurrentState != CombatCharacter.States.Dead && character.Direction != enemy.Direction)
                        character.Position = character.PreviousPosition; */
                }

                // Apply collision effects on collision
                if (characterRectangle.Intersects(enemyRectangle))
                {
                    if (CollisionHelper.IntersectPixels(characterRectangle, character.TextureData, enemyRectangle, enemy.TextureData))
                        ApplyCollisionEffects(character, enemy);
                }
            }
        }

        // Get the collision data
        public static Rectangle GetCollisionData(GameTime gameTime, CombatCharacter character)
        {
            var currentAnimation = character.CurrentAnimation;
            var frameLength = currentAnimation.Animation.FrameList[currentAnimation.FrameIndex].Width
                * character.CurrentAnimation.Animation.FrameList[character.CurrentAnimation.FrameIndex].Height;
            var characterRectangle = new Rectangle((int)character.Position.X, (int)character.Position.Y,
                currentAnimation.Animation.FrameList[currentAnimation.FrameIndex].Width, currentAnimation.Animation.FrameList[currentAnimation.FrameIndex].Height);

            // Load collision data
            var textureData = character.TextureData;

            if (textureData.Length != frameLength)
            {
                Array.Resize(ref textureData, frameLength);
                currentAnimation.Animation.SpriteTexture.GetData(0, currentAnimation.Animation.FrameList[currentAnimation.FrameIndex], textureData, 0, textureData.Length);
                character.TextureData = textureData;
            }

            return characterRectangle;
        }
    }
}
